package com.yeeverse.sdk.api;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.type.TypeReference;
import com.yeeverse.sdk.constants.SdkConstants;
import com.yeeverse.sdk.exception.SdkException;
import com.yeeverse.sdk.util.*;
import com.yeeverse.sdk.util.http.HttpUtils;
import com.yeeverse.sdk.vo.ReqDataVO;
import com.yeeverse.sdk.vo.RespHeadDataVO;
import com.yeeverse.sdk.vo.api.*;
import com.yeeverse.sdk.vo.api.nft.*;
import com.yeeverse.sdk.vo.api.subMerchantApply.ApiUploadRequestDTO;
import org.apache.http.Header;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * 统一支付api
 *
 * @author sinhoo
 */
public class PaycenterApi {

    private static final Logger logger = LoggerFactory.getLogger(PaycenterApi.class);

    public static final String PARAM_HEAD = "head";
    public static final String PARAM_BODY = "body";

    /**
     * sdk配置
     */
    private final SdkContext sdkContext;

    public PaycenterApi() {
        //初始化环境变量
        sdkContext = new SdkContextBuilder().initContext();
    }

    public PaycenterApi(String appId, String privateKey, String publictKey, String baseUrl) {
        //初始化环境变量
        sdkContext = new SdkContextBuilder(appId, privateKey, publictKey, baseUrl).initContext();
    }

    /**
     * 通用请求方法，可以自己封装参数
     *
     * @param request 请求参数
     * @param url     请求地址
     * @param tclazz  响应映射类型
     * @return 响应结果
     */
    public <T> T baseRequest(Map<String, Object> request, String url, Class<T> tclazz) {
        String response = callApi(url, request);
        return JsonUtils.fromJsonString(response, tclazz);
    }


    public <T> T baseRequest(BaseRequest request, String url, Class<T> tclazz) {
        String response = callApi(url, JsonUtils.parseBean2Map(request));
        return JsonUtils.fromJsonString(response, tclazz);
    }

    public Map baseRequest(BaseRequest request, String url) throws Exception {
        String response = callApi(url, JsonUtils.parseBean2Map(request));
        return JsonUtils.fromJsonString(response, Map.class);
    }

    /**
     * 链账户创建
     */
    public CreateAccountRespDTO createChainAccount(CreateAccountReqDTO request) throws Exception {
        return baseRequest(request, SdkContants.NFT_CREATE_ACCOUNT_URL, CreateAccountRespDTO.class);
    }

    /**
     * 链上账户创建查询
     */
    public CreateAccountRespDTO queryChainAccount(QueryAccountReqDTO request) throws Exception {
        return baseRequest(request, SdkContants.NFT_QUERY_ACCOUNT_URL, CreateAccountRespDTO.class);
    }

    /**
     * 用户NFT藏品查询
     */
    public QueryAccountNFTRespDTO queryAccountNFT(QueryAccountNFTReqDTO request) throws Exception {
        return baseRequest(request, SdkContants.NFT_QUERY_ACCOUNT_NFT_URL, QueryAccountNFTRespDTO.class);
    }

    /**
     * 铸造发行
     */
    public CreateNFTRespDTO createNFT(CreateNFTReqDTO request) throws Exception {
        return baseRequest(request, SdkContants.NFT_CREATE_NFT_URL, CreateNFTRespDTO.class);
    }

    /**
     * 创建藏品系列
     */
    public CreateSeriesRespDTO createSeries(CreateSeriesReqDTO request) throws Exception {
        return baseRequest(request, SdkContants.NFT_CREATE_SERIES_URL, CreateSeriesRespDTO.class);
    }

    /**
     * NFT转移
     */
    public NFTTransferRespDTO nftTransfer(NFTTransferReqDTO request) throws Exception {
        return baseRequest(request, SdkContants.NFT_TRANSFER_URL, NFTTransferRespDTO.class);
    }

    /**
     * NFT销毁
     */
    public NFTDestoryRespDTO nftDestory(NFTDestoryReqDTO request) throws Exception {
        return baseRequest(request, SdkContants.NFT_DESTORY_URL, NFTDestoryRespDTO.class);
    }

    /**
     * 查询NFT藏品铸造结果
     */
    public QueryCreateNFTRespDTO queryCreateNFT(QueryCreateNFTReqDTO request) throws Exception {
        return baseRequest(request, SdkContants.NFT_QUERY_CREATE_NFT_URL, QueryCreateNFTRespDTO.class);
    }

    /**
     * 查询NFT操作结果(转移或销毁或系列创建)
     */
    public QueryNFTOperateRespDTO queryNFTOperate(QueryNFTOperateReqDTO request) throws Exception {
        return baseRequest(request, SdkContants.NFT_QUERY_NFT_OPERATE_URL, QueryNFTOperateRespDTO.class);
    }


    /**
     * 退款
     */
    public RefundResponse refund(RefundRequest request) throws Exception {
        return baseRequest(request, SdkContants.REFUND_URL, RefundResponse.class);
    }

    /**
     * 退款查询
     */
    public QueryRefundResponse queryRefund(QueryRefundRequest request) throws Exception {
        return baseRequest(request, SdkContants.QUERY_REFUND_URL, QueryRefundResponse.class);
    }

    /**
     * 关单
     */
    public CloseResponse close(CloseRequest request) throws Exception {
        return baseRequest(request, SdkContants.CLOSE_URL, CloseResponse.class);
    }

    /**
     * 账单下载
     *
     * @param request
     * @return
     * @throws Exception
     */
    public String downloadBill(DownloadBillRequest request) throws Exception {
        ReqDataVO reqData = new ReqDataVO();
        reqData.setT(request);
        Map<String, Object> paramsMap = new HashMap<>();
        paramsMap.put("reqContent", reqData.toString());
        return HttpUtils.httpGet(SdkContext.getCurrentSdk().getBaseUrl() + SdkContants.DOWNLOAD_URL, paramsMap);
    }

    private static void catFiles(WritableByteChannel target, String[] files) throws Exception {
        for (int i = 0; i < files.length; i++) {
            FileInputStream fis = new FileInputStream(files[i]);
            FileChannel channel = fis.getChannel();
            channel.transferTo(0, channel.size(), target);
            channel.close();
            fis.close();
        }
    }

    /**
     * api文件上传
     */
    public Map apiUploadRequest(String url, ApiUploadRequestDTO request) throws Exception {
        try {
            String decryptStr = PayUploadApi.sendUploadFile(url, request);
            logger.info("上传文件返回数据: {}", decryptStr);
            return JsonUtils.fromJsonString(doDecripytReponseV2(decryptStr), Map.class);
        } finally {
            try {
                if (null != sdkContext) {
                    sdkContext.destory();
                }
            } catch (Exception e) {
            }
        }
    }

    /**
     * 请求v4接口
     * 请求数据采用json交互，使用rsa验签
     *
     * @param url     接口地址
     * @param request 请求请求参数
     * @return 响应结果
     */
    public String callApi(String url, Map<String, Object> request) {
        // 参数校验
        if (Objects.isNull(url) || Objects.isNull(request)) {
            throw new SdkException("url或请求参数不能为空");
        }

        CloseableHttpClient httpClient = null;
        try {
            httpClient = HttpClients.custom().setSSLSocketFactory(getSslConfig()).build();

            // 创建请求
            HttpPost httpPost = new HttpPost(sdkContext.getBaseUrl() + url);
            httpPost.setHeader(HttpHeaders.CONTENT_TYPE, "application/json;charset=utf-8");
            httpPost.setHeader(SdkConstants.HEADER_REQUEST_APPID_KEY, sdkContext.getAppId());
            httpPost.setHeader(SdkConstants.HEADER_REQUEST_SDK_VERSION_KEY, SdkConstants.HEADER_REQUEST_SDK_VERSION_VALUE);

            // 获取签名内容
            String requestSign = getSignData(request);
            httpPost.setHeader(SdkConstants.HEADER_SIGN_KEY, requestSign);

            // 请求内容
            httpPost.setEntity(new StringEntity(JsonUtils.toJsonString(request), StandardCharsets.UTF_8));

            // 发送请求
            CloseableHttpResponse httpResponse = httpClient.execute(httpPost);

            // 获取响应
            int statusCode = httpResponse.getStatusLine().getStatusCode();
            if (HttpStatus.SC_OK != statusCode) {
                throw new SdkException("接口响应失败，statusCode = " + statusCode);
            }

            // 响应结果
            String responseBody = EntityUtils.toString(httpResponse.getEntity());
            Header responseIdHeader = httpResponse.getFirstHeader(SdkConstants.HEADER_RESPONSE_ID_KEY);
            if (Objects.nonNull(responseIdHeader)) {
                logger.info("接口响应内容：{}, 响应id：{}", responseBody, responseIdHeader.getValue());
            }

            // 响应签名
            Header signHeader = httpResponse.getFirstHeader(SdkConstants.HEADER_SIGN_KEY);
            if (Objects.isNull(signHeader)) {
                logger.info("接口响应内容：{}", responseBody);
                throw new SdkException("响应验签失败，响应签名为空");
            }
            String responseSign = signHeader.getValue();

            // 响应验签
            checkResponseSing(responseBody, responseSign);

            return responseBody;
        } catch (Exception e) {
            throw new SdkException("请求接口报错", e);
        } finally {
            if (Objects.nonNull(httpClient)) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    logger.error("关闭客户端报错", e);
                }
            }
        }
    }

    /**
     * 响应签名验签
     *
     * @param responseBody 响应结果
     */
    private void checkResponseSing(String responseBody, String responseSign) {
        if (StringUtils.isBlank(responseBody)) {
            throw new SdkException("接口响应失败，返回内容为空");
        }
        if (StringUtils.isBlank(responseSign)) {
            throw new SdkException("接口响应失败，签名数据为空");
        }

        try {
            // 获取响应签名字符串
            Map<String, Object> responseDataMap = JsonUtils.fromJsonString(responseBody, new TypeReference<Map<String, Object>>() {
            });
            String signRowData = getSignRowData(responseDataMap);

            // 验签
            boolean verify = RSAUtils.verify(signRowData.getBytes(StandardCharsets.UTF_8), sdkContext.getPublictKey(), responseSign);
            if (!verify) {
                throw new SdkException("响应验签失败");
            }
        } catch (Exception e) {
            throw new SdkException("响应验签失败", e);
        }
    }

    /**
     * 获取签名
     * 按照Key升序排序，拼接key=value,使用&分隔
     *
     * @param request
     * @return
     */
    private String getSignData(Map<String, Object> request) {
        try {
            String realRowData = getSignRowData(request);
            return RSAUtils.sign(realRowData.getBytes(StandardCharsets.UTF_8), sdkContext.getPrivateKey());
        } catch (Exception ex) {
            throw new SdkException("签名异常", ex);
        }
    }

    /**
     * 获取签名的原文数据
     * v1&v2
     *
     * @param dataMap
     * @return
     */
    private String getSignRowData(Map<String, Object> dataMap) {
        // 按照字母升序排序
        List<String> keyList = new ArrayList<>(dataMap.keySet());
        Collections.sort(keyList);

        // 拼接加密传原文
        StringBuilder rowData = new StringBuilder();
        for (String key : keyList) {
            Object value = dataMap.get(key);
            if (Objects.nonNull(value)) {
                rowData.append(value);
                rowData.append(SdkConstants.STRING_SEPARATER);
            }
        }
        return rowData.substring(0, rowData.lastIndexOf(SdkConstants.STRING_SEPARATER));
    }

    /**
     * 获取SSL配置，忽略证书
     *
     * @return
     */
    private SSLConnectionSocketFactory getSslConfig() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
        return new SSLConnectionSocketFactory(
                SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build(),
                NoopHostnameVerifier.INSTANCE);
    }

    /**
     * 对返回数据进行v2格式处理
     *
     * @param httpResponse
     * @return
     * @throws Exception
     */
    private String doDecripytReponseV2(String httpResponse) throws Exception {
        JSONObject jsonObject = JSONObject.parseObject(httpResponse);
        if (jsonObject.containsKey(PARAM_HEAD) || jsonObject.containsKey(PARAM_BODY)) {
            String head = jsonObject.getString(PARAM_HEAD);
            String body = jsonObject.getString(PARAM_BODY);
            RespHeadDataVO respHeadDataVO = JSONObject.parseObject(head, RespHeadDataVO.class);
            //解密aes密钥
            String aesKey = RSAUtils.decryptByPrivateKey(respHeadDataVO.getAesKey(), SdkContext.getCurrentSdk().getPrivateKey());

            //解密响应业务报文
            String decryptStr = AES128CBC.decrypt(body, aesKey);
            //报文验签
            if (!RSAUtils.verify(decryptStr.getBytes(StandardCharsets.UTF_8), SdkContext.getCurrentSdk().getPublictKey(), respHeadDataVO.getSign())) {
                throw new SdkException("响应报文验签失败");
            }
            logger.info("解密明文: {}", decryptStr);
            return decryptStr;
        } else {
            return jsonObject.toJSONString();
        }
    }
}
